﻿using System;
using System.Threading;
using System.Net;
using System.Net.Sockets;
using System.Threading.Tasks;

namespace AsynchronousMethods.NonblockingTPL
{
  public class Server
  {
    public void Serve (IPAddress address, int port)
    {
      ThreadPool.SetMinThreads (50, 50);
      TcpListener listener = new TcpListener (address, port);
      listener.Start ();
      while (true)
      {
        TcpClient c = listener.AcceptTcpClient ();
        new ReverseEcho ().BeginAsync (c);
      }
    }
  }

  class ReverseEcho
  {
    TcpClient _client;
    NetworkStream _stream;
    byte [] _data = new byte [5000];
    int _bytesRead = 0;

    internal void BeginAsync (TcpClient c)
    {
      _client = c;
      _stream = c.GetStream ();

      var task = Task.Factory.StartNew (Read);

      // Set up centralized error handling and cleanup:

      task.ContinueWith (ant =>
        Console.WriteLine ("Error: " + ant.Exception.Message),
        TaskContinuationOptions.OnlyOnFaulted);

      task.ContinueWith (ant =>
      {
        if (_stream != null) _stream.Close ();
        if (_client != null) _client.Close ();
      });
    }

    void Read ()    // This will create a child task.
    {
      Task<int> readChunk = Task<int>.Factory.FromAsync (
        _stream.BeginRead, _stream.EndRead,
        _data, _bytesRead, _data.Length - _bytesRead, null,
        TaskCreationOptions.AttachedToParent);

      // NB: Note that we need TaskContinuationOptions.AttachedToParent to ensure that 
      // the parent task's cleanup continuations don't fire until we're finishing
      // writing the response. The first printing of the book is missing this!
      readChunk.ContinueWith (Write, TaskContinuationOptions.NotOnFaulted 
                                   | TaskContinuationOptions.AttachedToParent);
    }

    void Write (Task<int> readChunk)
    {
      _bytesRead += readChunk.Result;
      if (readChunk.Result > 0 && _bytesRead < _data.Length)
      {
        Read ();       // More data to read!
        return;
      }
      Array.Reverse (_data);
      Task.Factory.FromAsync (_stream.BeginWrite, _stream.EndWrite,
                              _data, 0, _data.Length, null,
                              TaskCreationOptions.AttachedToParent);
    }
  }
}
